﻿Imports System.Threading


''' <summary>
''' This class acts as a simple network integrity monitor.  It monitors network integrity utilizing the
''' .NET "My.Computer.Network" API, most importantly the Ping functionality.  By periodically
''' "pinging" one or more remote hosts basic network availability can be determined.
''' Configurable settings for netowrk monitoring are: Host Targets, Ping Timeout, Ping Frequency,
''' 
''' Test Results                                         Status
''' ============================================================
''' Network is Available and Ping Succeeds               [Okay]
''' Network is Unavailable                               [Error]
''' Initial Ping Fails                                   [WARNING]
''' Subsequent Ping Fails (Downtime Threshold Exceeded)  [ERROR] 
''' </summary>
''' <remarks></remarks>
Public Class NetworkMonitor

    ' Some default values
    Public Const DEF_PING_TIMEOUT = 3000
    Public Const DEF_PING_TARGETS = "www.google.com"

    ' Network Status Values
    Public Enum NetworkStatus
        Unknown = -1
        Okay = 0
        Warning = 1
        Err = 2
    End Enum

    ' -------------------------------------------------------------------------
    ' Member variables
    ' -------------------------------------------------------------------------
    '
    ' The main worker thread so that monitoring happens in the backgound
    Private mWorker As Thread

    ' Tracks the current network status
    Private mStatus As NetworkStatus = NetworkStatus.Unknown

    ' Tracks the start time when a network ping fails.
    Private mDownStart As DateTime = Nothing

    ' How often in seconds to perform ping test
    Private mTestFrequency As Long

    ' How long in seconds can a network outage last before we raise the status to Error.
    Private mErrorThreshold As Long

    ' Message associated with current status if any
    Private mStatusMessage As String = ""

    ' How long in miliseconds before the ping test times out with a failure
    Private mPingTimeout As Long

    ' A single comma seperated string of target hosts to ping
    Private mPingTargets As String

    Private mContinueMonitoring As Boolean = True

    ' -------------------------------------------------------------------------
    ' Event Definitions
    ' -------------------------------------------------------------------------
    '
    ' This event is fired whenever the Network status changes
    Public Event StatusChanged(ByVal sender As NetworkMonitor, ByVal e As NetworkEventArgs)

    ' -------------------------------------------------------------------------
    ' Property Definitions
    ' -------------------------------------------------------------------------
    '
    ''' <summary>
    ''' Status Property returns current netowrk status
    ''' </summary>
    Public ReadOnly Property Status As NetworkStatus
        Get
            Return mStatus
        End Get
    End Property

    ''' <summary>
    ''' StatusMessage Property returns current message text assocaited with Status
    ''' </summary>
    Public ReadOnly Property StatusMessage As String
        Get
            Return mStatusMessage
        End Get
    End Property

    ''' <summary>
    ''' PingTimeout Property can be set to determine how long a Ping test should wait before
    ''' indicating a failure.  
    ''' </summary>
    ''' <value>Timeout given as milliseconds.</value>
    Public Property PingTimeout As Long
        Get
            Return mPingTimeout
        End Get
        Set(ByVal value As Long)
            mPingTimeout = value
        End Set
    End Property

    ''' <summary>
    ''' A single string of comma seperate hosts to ping
    ''' </summary>
    ''' <value>String of target hosts</value>
    Public Property PingTargets As String
        Get
            Return mPingTargets
        End Get
        Set(ByVal value As String)
            mPingTargets = value
        End Set
    End Property

    ' -------------------------------------------------------------------------
    ' Main Code
    ' -------------------------------------------------------------------------

    ''' <summary>
    ''' Contructor.
    ''' </summary>
    ''' <param name="pingFrequency">How often in seconds to test the network via Ping tests.</param>
    ''' <param name="alarmThreshold">How long to wait in seconds since last Ping failure before 
    ''' elevating network status from Warning to Error. </param>
    ''' <remarks>The startMonitoring() method must be called after the creation of new NetworkMonitor 
    ''' object to begin the actual monitoring.</remarks>
    Public Sub New(ByVal pingFrequency As Long, ByVal alarmThreshold As Long)
        mTestFrequency = pingFrequency
        mErrorThreshold = alarmThreshold

        ' This the main worker background thread which will be started when startMonitoring() is called.
        ' The 'doMonitoring' method is what the Thread will call and run when started.
        mWorker = New Thread(AddressOf doMonitoring)

    End Sub

    Public Sub startMontoring()
        mWorker.Start()
    End Sub

    Sub stopMonitoring()
        mContinueMonitoring = False
    End Sub


    ''' <summary>
    ''' Private method to set the current Network Status.  Primary purpose of this method is to
    ''' detect a change in Status and fire off an Event to Listeners when a change occurs.
    ''' </summary>
    ''' <param name="status"></param>
    ''' <param name="message"></param>
    Private Sub setStatus(ByVal status As NetworkStatus, ByVal message As String)
        If status <> mStatus Then
            ' status has changed
            mStatus = status
            mStatusMessage = message
            onStatusChanged(New NetworkEventArgs(Me.Status, Me.StatusMessage))
        End If
    End Sub

    ''' <summary>
    ''' Handles the firing of the internal event to any Listeners.
    ''' </summary>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub onStatusChanged(ByVal e As NetworkEventArgs)
        RaiseEvent StatusChanged(Me, e)
    End Sub

    ''' <summary>
    ''' Handles the Ping test across one or more Ping host targets
    ''' </summary>
    ''' <returns>True if any of the hosts can be Ping'ed prior to a timeout.</returns>
    ''' <remarks>Host are taken from the PingTargets Property</remarks>
    Private Function canPingTargets() As Boolean
        Dim hostReached As Boolean = False
        Dim targets As String() = Split(mPingTargets, ",")
        Try
            For Each host As String In targets
                hostReached = My.Computer.Network.Ping(host, mPingTimeout)
                ' Just need one good Ping.
                If hostReached Then
                    Exit For
                End If
            Next
        Catch ex As Exception
            hostReached = False
        End Try

        Return hostReached

    End Function

    ''' <summary>
    ''' The main monitoring method which is invoked by the worker thread. The method contains
    ''' a infinite while loop so as to never release the worker thread from monitoring until
    ''' the NetworkMonitor object instance is destroyed.
    ''' </summary>
    ''' <remarks>Network Status is determined and set from this mentod.</remarks>
    Private Sub doMonitoring()
        While mContinueMonitoring
            ' First perform a basic Network availability test
            If My.Computer.Network.IsAvailable Then
                ' Nest try to Ping the host tragets provided
                If canPingTargets() Then
                    ' Ping Succeeded. Network is good
                    mDownStart = Nothing
                    setStatus(NetworkStatus.Okay, "Network is available and reachable.")
                Else
                    ' Ping Failed. Network is momentarily unreachable.
                    ' If this is the first Ping failure we assume a temporary network outage
                    ' as can happen on a busy network or weak wireless coneection.
                    ' However we note the time of failure so we can elevate the Status if needed.
                    If mDownStart = Nothing Then
                        mDownStart = Date.Now
                    End If

                    ' Calulate the total elapsed time (in seconds) network has been down.
                    Dim downTime As Long = DateDiff(DateInterval.Second, mDownStart, Date.Now)

                    ' If elapsed time is greater than provided Threshold elevate Status to Error
                    If downTime >= mErrorThreshold Then
                        ' Downgrade/Elevate status level to Error 
                        setStatus(NetworkStatus.Err, "Network has been unreachable for more than " _
                                  & mErrorThreshold & " seconds!")
                    Else
                        ' Keep Status level at Warning
                        setStatus(NetworkStatus.Warning, "Network is available but has become unreachable.")
                    End If
                End If
            Else
                ' Basic Network Availibility Test Failed.  Set Status Level to Error.
                setStatus(NetworkStatus.Err, "Network has become unavailable!")
            End If
            ' Wait then test again
            Thread.Sleep(mTestFrequency * 1000) ' Convert seconds to milliseconds
        End While
    End Sub

    ''' <summary>
    ''' Ensures every configuration settings has a valid value.  Preference is to use values
    ''' stored in the application settings, followed by defined defaults.
    ''' </summary>
    ''' <remarks></remarks>
    Sub processConfiguration()

        mPingTimeout = My.Settings.PING_TIMEOUT
        If mPingTimeout < 0 Then
            mPingTimeout = DEF_PING_TIMEOUT
        End If

        mPingTargets = My.Settings.PING_TARGETS
        If mPingTargets = "" Then
            mPingTargets = DEF_PING_TARGETS
        End If

    End Sub

    ' Inner Class To Handle Event Args
    Public Class NetworkEventArgs
        Inherits EventArgs

        Private mStatus As NetworkStatus = NetworkStatus.Okay
        Private mMessage As String = ""
        Private mTime As DateTime

        Public ReadOnly Property Status As NetworkStatus
            Get
                Return mStatus
            End Get
        End Property

        Public ReadOnly Property Message As String
            Get
                Return mMessage
            End Get
        End Property

        Public ReadOnly Property Time As DateTime
            Get
                Return mTime
            End Get
        End Property

        Public Sub New(ByVal status As NetworkStatus, ByVal message As String)
            Me.New(status, message, DateTime.Now)
        End Sub

        Private Sub New(ByVal status As NetworkStatus, ByVal message As String, ByVal time As Date)
            MyBase.New()
            mStatus = status
            mMessage = message
            mTime = time
        End Sub

    End Class

End Class
